#include <iostream>
#include <string>
#include <cstdlib>
#include <map>
#include <ctime>

#include "hunterAPI.hpp"
#include "hunterHTTP.hpp"

using namespace std;

const double 
	     _PROB_BE_HUNTER_LAST_HUNTER = 0.23,
	     _PROB_BE_HUNTER_LAST_PREY   = 0.74,
	     _PROB_BE_HUNTER_NEW         = 0,      // confirmed.
	     _GAUSS_DELTA                = 1 / sqrt(2 * 2 * acos(0.0)),
	     _PRIORITY_RATING_MEETING    = 1,
	     _MATH_EXP                   = 2.71828182;

int 
	shuffle(vector <int> &idlers, HunterAPI::HiveMind &hm)
	{
		cerr << "shuffle: Greetings\n";
		
		random_shuffle(idlers.begin(), idlers.end());
		
		vector <int> hunter, prey; // they are NOW hunters or preys
		vector <double> probability(idlers.size());
		
		for (int i = 0; i < idlers.size(); i++)
		{
			vector <HunterAPI::Hunt> last_hunt; // size never more then 1
			if (hm.getLastNHunts(idlers[i], last_hunt, 1))
			{
				cerr << "Error in shuffle()";
				return true;
			}
			
			if (last_hunt.empty())
				probability[i] = _PROB_BE_HUNTER_NEW;
			else if (last_hunt[0].guys.first == idlers[i])
				probability[i] = _PROB_BE_HUNTER_LAST_HUNTER;
			else
				probability[i] = _PROB_BE_HUNTER_LAST_PREY;
		}
		
		cerr << "shuffle: Initial probability distributed:";
		for (int i = 0; i < probability.size(); i++)
			cerr << probability[i] << ", ";
		cerr << "\n";
		
		srand(time(0));
		bool push_rest_in_hunters = false,
		     push_rest_in_preys   = false;
		int index = idlers.size();
		
		for (int i = 0; i < idlers.size(); i++)
		{
			if (i >= idlers.size() / 2 && (int) hunter.size() - (int) prey.size() >= (int) idlers.size() - i)
			{
				push_rest_in_preys = true; 
				index = i;
				break;
			}
			if (i >= idlers.size() / 2 && (int) prey.size() - (int) hunter.size() >= (int) idlers.size() - i)
			{
				push_rest_in_hunters = true;
				index = i;
				break;
			}
			
			double rnd = (1.0 * rand()) / RAND_MAX;
//			double rnd = 50;
			if (rnd < probability[i])
				hunter.push_back(idlers[i]);
			else
				prey.push_back(idlers[i]);
		}

		cerr << "\tprey: ";
		for (int i = 0; i < prey.size(); i++)
			cerr << prey[i] << ", ";
		cerr << "\n\thunter: ";
		for (int i = 0; i < hunter.size(); i++)
			cerr << hunter[i] << ", ";
		cerr << "\n";
				
		for (int i = index; i < idlers.size(); i++) 
		{
			if (push_rest_in_preys)
				prey.push_back(idlers[i]);
			else if (push_rest_in_hunters)
				hunter.push_back(idlers[i]);
		}

		cerr << "shuffle: Players distributed\n";
		cerr << "\tprey: ";
		for (int i = 0; i < prey.size(); i++)
			cerr << prey[i] << ", ";
		cerr << "\n\thunter: ";
		for (int i = 0; i < hunter.size(); i++)
			cerr << hunter[i] << ", ";
		cerr << "\n";

		map <int, double> tmp; // lazy for worktime opt.
		
		// sorting by rating
		sort(prey.begin(), prey.end(), [&](int id1, int id2) 
			{
				if (tmp.find(id1) == tmp.end())
					tmp[id1] = hm.getRatingById(id1);
				if (tmp.find(id2) == tmp.end())
					tmp[id2] = hm.getRatingById(id2);
					
				return tmp[id1] < tmp[id2];
			});
		
		sort(hunter.begin(), hunter.end(), [&](int id1, int id2) 
			{
				if (tmp.find(id1) == tmp.end())
					tmp[id1] = hm.getRatingById(id1);
				if (tmp.find(id2) == tmp.end())
					tmp[id2] = hm.getRatingById(id2);
					
				return tmp[id1] < tmp[id2];
			});			
		
		tmp.clear();
		
		// find a prey for each listed hunter
		vector <pair <int, int> > res;
		vector <bool> used(prey.size(), false); // is this prey distributed already
		probability.clear(), probability.resize(prey.size());
		
		for (int i = 0; i < hunter.size(); i++)
		{
			probability.assign(probability.size(), 0.0);
		
			// nearest are dearest (by rating)
			int pt = upper_bound(prey.begin(), prey.end(), hunter[i]) - prey.begin();
			
			for (int j = 0; j < probability.size(); j++)
			{
				if (used[j])
					continue;
				probability[j] = _GAUSS_DELTA * pow(_MATH_EXP, -1.0 * (j - pt) * (j - pt) / 2);
			}
			
			// sort a probability according to the frequency of games played in this pair
			vector <int> hunt_cnt(prey.size());
			double sum = 0;
			for (int j = 0; j < prey.size(); j++)
			{
				if (used[j])
					continue;
				hunt_cnt[j] = hm.countHuntsOfPair(hunter[i], prey[j]);
				sum += hunt_cnt[j];
			}

			for (int i = 0; i < probability.size(); i++)
				cerr << fixed << setprecision(4) << probability[i] << ",";
			cerr << "\n";
			
			double probability_integral = 0;
			for (int j = 0; j < prey.size(); j++)
			{
				if (used[j])
					continue;
				double probability_new = _PRIORITY_RATING_MEETING * probability[j] + ((sum == 0) ? 0 : hunt_cnt[j] / sum);
				probability_integral += probability_new;
				probability[j] += ((j == 0) ? probability_new : probability_new + probability[j - 1]);
			}
			
			for (int i = 0; i < probability.size(); i++)
				cerr << fixed << setprecision(4) << probability[i] << " ";
			cerr << "\n";

			// choose
			int rnd = rand() / RAND_MAX * (probability_integral);
			for (int j = 0; j < probability.size(); j++)
			{
				if (rnd > probability[j])
					continue;
				used[j] = true;
				res.push_back(make_pair(hunter[i], prey[j]));
				break;
			}
		}
		
		// create Hunts by res
		int cnt = 0;
		for (int i = 0; i < res.size(); i++)
		{
			HunterAPI::Hunt h;
			h.guys = res[i];
			h.classes = make_pair(hm.giveClass(res[i].first), hm.giveClass(res[i].second));
			h.codes = make_pair(HunterAPI::getNewCode(), HunterAPI::getNewCode());
			if (!hm.createNewHunt(h))
				cnt++;
		}
		
		return cnt;
	}

int main()
{
	HunterAPI::HiveMind hm("./database");
	freopen("./log", "w+", stderr);

	string cookie = getCookie();
	string query = getQuery();
	
	int id = -1;
	if (hm.verifyCookie(cookie, id) || id == -1)
	// login failed
	{
		cout << "Content-type: text/html;\n\n";
		o_header("Fail");
		o_login_error("Session expired w/ " + cookie);
		o_footer();
		return 0;
	}
	
	HunterAPI::Player pl;
	hm.getPlayerById(id, pl);

	if (find(pl.stats.begin(), pl.stats.end(), 'Q') == pl.stats.end())
	// not a Q
	{
		hm.banPlayer(pl);
		
		cout << "Content-type: text/html;\n\n";
		o_header("Security violation!");
		cout << "Your request had caused <h2>Security Violation</h2><br>This incident will be reported.<br> Moreover, your membership is now banned until activated manually by administrator.";
		o_footer();
		
		hm.dropCookies(pl.id);
		return 0;
	}

	ostringstream out;
	bool sqlerr = false;	
	string qry;
	if (query == "sql")
	{
		
		getline(cin, qry);
		qry = HunterAPI::parseHTTPinput(qry)["sql"];
		vector <pair <string, string> > ans;
		string e = hm.justAsk(qry, ans);
		if (e == "")
		{
			out << "<div style = \"border:2px solid green;\"><i>" << qry << "</i> proceed OK</div><br>";
			out << "Total " << ans.size() << " lines below:<br><table>";
			for (int i = 0; i < ans.size(); i++)
				out << "<tr><td>" << ans[i].first << "</td><td>" << ans[i].second << "</td></tr>";
			out << "</table>";
		}
		else
		{
			out << "SQL error: " << e;
			sqlerr = true;
		}
		out << "<hr>";
	}
	
	if (query == "shuffle")
	{
		vector <int> idlers;
		hm.getIdleIds(idlers);
				
		vector <HunterAPI::Hunt> pairs;
		int cnt = shuffle(idlers, hm);
		
		out << "Shuffled " << 2 * cnt << " players OK.<br>";
	}

	// Q page goes here
	cout << "Content-type: text/html;\n\n";	
	o_header("Club -- Alchemy");
	o_upper_visible(pl);
	
	cout << "<hr><form method = \"post\" action = \"" << path_home << "/cgi-bin/alchemy.cgi?query=sql\"><input name = \"sql\" type = \"text\" value = \"select p_id, p_username from Players where p_engaged is null\" style = \"visibility:hidden;\"><input type = \"submit\" value = \"List IDLErs\"></form>";
	cout << "<form method = \"post\" action = \"" << path_home << "/cgi-bin/alchemy.cgi?query=shuffle\">Shuffle players: &nbsp; <input type = \"submit\" value = \"Shuffle them!\"></form>";

		
	cout << "<hr><form method = \"post\" action = \"" << path_home << "/cgi-bin/alchemy.cgi?query=sql\">DB Query: &nbsp; <input name = \"sql\" type = \"text\" width = \"200\" value = \"" << ((sqlerr) ? qry : "") << "\"><input type = \"submit\"></form>";
	
	cout << out.str();
	
	o_footer();
	
	return 0;
}
